﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;
 
namespace aoi
{
    /**
     *  支持 无缝地图 的 aoi 系统
     *      . 默认情况下, AOI 内部都使用全局 cell 坐标
     */

    public class AoiObject
    {
        public AoiCell cell;            // 所属 cell
        public int id;
        public int x;                   // 全局 x/y 坐标(像素)
        public int y;
        public List<AoiObject> relate_list = new List<AoiObject>();
    }

    public class AoiCell
    {
        public List<AoiObject> objlist; // 对象列表, 如果位于边界区域, 则为 null
        public AoiScene scene;          // 主场景(所属场景)
        public AoiScene scene2;         // 副场景
    }

    public class AoiScene
    {
        public int id;                  // 唯一 id
        public Rectangle rect;          // 全局坐标范围
        public AoiCell[] cells;         // cell 数组

        public AoiCell GetCell(int x, int y)
        {
            return cells[(y - rect.Y) * rect.Width + (x - rect.X)];
        }
    }


    public delegate void AppearHandler(AoiObject obj, List<AoiObject> list);
    public delegate void DisappearHandler(AoiObject obj, List<AoiObject> list);
    public delegate void SceneChangedHandler(AoiObject obj, AoiScene scene1, AoiScene scene2);

    public static class AoiManager
    {
        public const int CELL_SIZE = 1;            // 每个 Cell 对应外部的 "像素" 尺寸
        public const int FOV_SIZE = 2;              // 视野尺寸

        public const int MAX_SCENE_WIDTH = 256;
        public const int MAX_SCENE_HEIGHT = 256;
        public const int MIN_SCENE_WIDTH = FOV_SIZE * 2;
        public const int MIN_SCENE_HEIGHT = FOV_SIZE * 2;

        public static event AppearHandler appearHandler;
        public static event DisappearHandler disappearHandler;
        public static event SceneChangedHandler sceneChangedHandler;

        public static List<AoiScene> scene_list = new List<AoiScene>();     // 全局场景列表
        static List<AoiObject> obj_list = new List<AoiObject>();

        /// <summary>
        /// 建立一个场景
        /// <param name="rect">全局 cell 尺寸</param>
        /// </summary>
        public static AoiScene CreateScene(int id, Rectangle rect)
        {
            Debug.Assert(rect.X >= 0
                && rect.Y >= 0
                && rect.Width >= MIN_SCENE_WIDTH
                && rect.Height >= MIN_SCENE_HEIGHT
                && rect.Width <= MAX_SCENE_WIDTH
                && rect.Height <= MAX_SCENE_HEIGHT);

            AoiScene new_scene = new AoiScene();
            new_scene.id = id;
            new_scene.rect = rect;

            // 建立 cells
            int count = rect.Width * rect.Height;
            new_scene.cells = new AoiCell[count];

            // 从现有 scene_list 中共享 cell
            foreach (var scene in scene_list)
            {
                Rectangle int_rect = Rectangle.Intersect(scene.rect, new_scene.rect);
                if (!int_rect.IsEmpty)
                {
                    int w = int_rect.Width;
                    int h = int_rect.Height;

                    int len1 = scene.rect.Width;
                    int pos1 = (int_rect.Y - scene.rect.Y) * len1 + (int_rect.X - scene.rect.X);
                    int add1 = len1 - w;

                    int len2 = new_scene.rect.Width;
                    int pos2 = (int_rect.Y - new_scene.rect.Y) * len2 + (int_rect.X - new_scene.rect.X);
                    int add2 = len2 - w;

                    for (int ty = 0; ty < h; ty++)
                    {
                        for (int tx = 0; tx < w; tx++)
                        {
                            // 共享 cell, 确保 scene2 是空的, 把它设置为 new_scene
                            AoiCell c = scene.cells[pos1++];
                            Debug.Assert(c != null && c.scene2 == null);
                            c.scene2 = new_scene;
                            new_scene.cells[pos2++] = c;
                        }
                        pos1 += add1;
                        pos2 += add2;
                    }
                }
            }

            // 初始化其它 cell
            for (int i = 0; i < count; i++)
            {
                if (new_scene.cells[i] == null)
                {
                    // 新建 cell, 初始时 new_scene 作为主场景
                    AoiCell c = new AoiCell();
                    c.scene = new_scene;
                    new_scene.cells[i] = c;
                }
            }

            // 初始化 中间区域 的对象列表, 非中间区域的 obj_list 为 null , 表示不可接收对象存放
            ForeachCell(new_scene, Rectangle.FromLTRB(FOV_SIZE, FOV_SIZE, rect.Width - FOV_SIZE, rect.Height - FOV_SIZE), (c) =>
            {
                if (c.objlist == null)
                {
                    // 新建 obj_list, 并且确保自己是 主场景 (因为另一个场景没有建立 obj_list, 证明该 cell 位于它的 边缘区域)
                    c.objlist = new List<AoiObject>();
                    if (c.scene != new_scene)
                    {
                        c.scene2 = c.scene;
                        c.scene = new_scene;
                    }
                }
            });

            // 把自己加入场景列表
            scene_list.Add(new_scene);
            return new_scene;
        }

        /// <summary>
        /// 输出 cell 信息
        /// </summary>
        public static void dump_cells(AoiScene scene)
        {
            Console.WriteLine("dump_cells, scene_id:{0}, rect:{1}", scene.id, scene.rect);
            for (int i = 0; i < scene.cells.Length; i++)
            {
                if (i > 0 && (i % scene.rect.Width) == 0)
                {
                    Console.WriteLine();
                }

                AoiCell c = scene.cells[i];
                Console.Write("{0}{1}{2} ", c.scene.id, (c.scene2 != null ? c.scene2.id : 0), (c.objlist != null ? "+" : "-"));
            }
            Console.WriteLine();
        }

        // 遍历场景中的 cell, range=局部坐标
        private static void ForeachCell(AoiScene scene, Rectangle range, Action<AoiCell> fn)
        {
            Rectangle rect = scene.rect;
            rect.X = rect.Y = 0;
            Debug.Assert(rect.Contains(range));     // 必须位于范围内

            int len = rect.Width;
            int pos = range.Y * len + range.X;
            int add = len - range.Width;
            AoiCell[] cells = scene.cells;

            for (int y = 0; y < range.Height; y++)
            {
                for (int x = 0; x < range.Width; x++)
                {
                    fn(cells[pos++]);
                }
                pos += add;
            }
        }

        // 获取对象列表
        private static List<AoiObject> GetObjectList(AoiScene scene, Rectangle range, AoiObject self, bool bClear = true)
        {
            if (bClear) obj_list.Clear();
            range.X -= scene.rect.X;
            range.Y -= scene.rect.Y;
            ForeachCell(scene, range, (c) =>
            {
                if (c.objlist != null)
                {
                    foreach (var o in c.objlist)
                    {
                        if (o != self)
                        {
                            obj_list.Add(o);
                        }
                    }
                }
            });
            return obj_list;
        }

        // 获取对象列表, 包含自己
        private static List<AoiObject> GetObjectList(AoiScene scene, int x, int y, int range, AoiObject self)
        {
            Rectangle rect = Rectangle.FromLTRB(x - range, y - range, x + range, y + range);
            return GetObjectList(scene, rect, self);
        }


        /// <summary>
        /// 添加对象
        /// </summary>
        static public void AddObject(AoiScene scene, AoiObject obj)
        {
            Debug.Assert(scene != null && obj != null && obj.cell == null);     // obj 必须不在场景中
            Console.WriteLine("add {0}", obj.id);

            int cx = obj.x / CELL_SIZE;
            int cy = obj.y / CELL_SIZE;
            AoiCell c = scene.GetCell(cx, cy);
            Debug.Assert(c != null && c.objlist != null);    // x/y 必须是有效位置
            Debug.Assert(c.scene == scene);

            c.objlist.Add(obj);
            obj.cell = c;

            if (appearHandler != null)
            {
                appearHandler(obj, GetObjectList(scene, cx, cy, FOV_SIZE, obj));
            }
        }

        /// <summary>
        /// 删除对象
        /// </summary>
        public static void RemoveObject(AoiObject obj)
        {
            Debug.Assert(obj != null && obj.cell != null);      // 必须位于 cell 中
            Console.WriteLine("remove {0}", obj.id);

            AoiCell c = obj.cell;
            c.objlist.Remove(obj);
            obj.cell = null;

            if (disappearHandler != null)
            {
                int cx = obj.x / CELL_SIZE;
                int cy = obj.y / CELL_SIZE;
                disappearHandler(obj, GetObjectList(c.scene, cx, cy, FOV_SIZE, obj));
            }
        }

        /// <summary>
        /// 移动对象
        /// <param name="x2,y2">全局像素坐标</param>
        /// </summary>
        public static void MoveObject(AoiObject obj, int x2, int y2)
        {
            Debug.Assert(obj != null && obj.cell != null);
            Console.WriteLine("move {0} {1},{2} => {3},{4}", obj.id, obj.x, obj.y, x2, y2);

            // 转换成 cell 尺寸
            int cx1 = obj.x / CELL_SIZE;
            int cy1 = obj.y / CELL_SIZE;
            int cx2 = x2 / CELL_SIZE;
            int cy2 = y2 / CELL_SIZE;
            obj.x = x2;
            obj.y = y2;
            if (cx1 == cx2 && cy1 == cy2) return;           // cell 坐标没变, 忽略

            // 移动 cell/scene
            AoiCell cell1 = obj.cell;
            AoiScene scene1 = cell1.scene;
            Debug.Assert(scene1.rect.Contains(cx2, cy2));   // 每次移动, 必须局限在同一个场景内

            AoiCell cell2 = scene1.GetCell(cx2, cy2);
            Debug.Assert(cell2.objlist != null);            // 目标位置 必须合法
            AoiScene scene2 = cell2.scene;

            cell1.objlist.Remove(obj);
            cell2.objlist.Add(obj);
            obj.cell = cell2;

            // 计算视野矩形
            Rectangle fov1 = Rectangle.FromLTRB(cx1 - FOV_SIZE, cy1 - FOV_SIZE, cx1 + FOV_SIZE, cy1 + FOV_SIZE);
            Rectangle fov2 = Rectangle.FromLTRB(cx2 - FOV_SIZE, cy2 - FOV_SIZE, cx2 + FOV_SIZE, cy2 + FOV_SIZE);

            Rectangle[] add_list = new Rectangle[2];
            Rectangle[] del_list = new Rectangle[2];
            int nadd = 0, ndel = 0, h0, h1;

            // 向右移动
            if (cx2 > cx1)
            {
                h0 = fov2.Left; h1 = fov1.Right;
                del_list[ndel++] = Rectangle.FromLTRB(fov1.Left, fov1.Top, h0, fov1.Bottom);
                add_list[nadd++] = Rectangle.FromLTRB(h1, fov2.Top, fov2.Right, fov2.Bottom);
            }
            // 向左移动
            else if (cx2 < cx1)
            {
                h0 = fov1.Left; h1 = fov2.Right;
                del_list[ndel++] = Rectangle.FromLTRB(h1, fov1.Top, fov1.Right, fov1.Bottom);
                add_list[nadd++] = Rectangle.FromLTRB(fov2.Left, fov2.Top, h0, fov2.Bottom);
            }
            // 水平静止
            else
            {
                h0 = fov1.Left; h1 = fov1.Right;
            }

            // 向下移动
            if (cy2 > cy1)
            {
                del_list[ndel++] = Rectangle.FromLTRB(h0, fov1.Top, h1, fov2.Top);
                add_list[nadd++] = Rectangle.FromLTRB(h0, fov1.Bottom, h1, fov2.Bottom);
            }
            // 向上移动
            else if (cy2 < cy1)
            {
                del_list[ndel++] = Rectangle.FromLTRB(h0, fov2.Bottom, h1, fov1.Bottom);
                add_list[nadd++] = Rectangle.FromLTRB(h0, fov2.Top, h1, fov1.Top);
            }
            // 垂直静止
            else
            {
            }

            // 消失的部分
            if (disappearHandler != null)
            {
                obj_list.Clear();
                for (int i = 0; i < ndel; i++)
                {
                    GetObjectList(scene1, del_list[i], obj, false);
                }
                if (obj_list.Count > 0) disappearHandler(obj, obj_list);
            }

            // 出现的部分
            if (appearHandler != null)
            {
                obj_list.Clear();
                for (int i = 0; i < nadd; i++)
                {
                    GetObjectList(scene2, add_list[i], obj, false);
                }
                if (obj_list.Count > 0) appearHandler(obj, obj_list);
            }

            // 场景变更
            if (sceneChangedHandler != null)
            {
                if (scene1 != scene2)
                {
                    sceneChangedHandler(obj, scene1, scene2);
                }
            }
        }
    }
}
